(ns instant.util.hsql
  (:require [honey.sql :as hsql]))

;; ----------------------
;; pg-hints custom clause

(defn add-args! [^StringBuilder s args]
  (when (seq args)
    (loop [s (.append s (hsql/format-entity (first args)))
           more (next args)]
      (when more
        (recur (-> s
                   (.append " ")
                   (.append (hsql/format-entity (first more))))
               (next more))))))

;; pg-hints expects a list of hints, e.g.
;; {:select :*
;;  :pg-hints [(index-scan :t2 :ea_index)]
;;  :from :triples}
;;
;; Can only be included once per query
(hsql/register-clause!
 :pg-hints
 (fn [_clause exps]
   (if-not (seq exps)
     []
     (let [s (StringBuilder.)]
       (.append s "/*+\n")
       (doseq [[op & args] exps]
         (.append s (hsql/sql-kw op))
         (.append s \()
         (case op
           :'Rows (do (add-args! s (butlast args))
                      (.append s " #")
                      (assert (number? (last args)))
                      (.append s (last args)))


           :'Parallel (do (add-args! s (take 1 args))
                          (.append s " ")
                          (assert (number? (second args)))
                          (.append s (second args))
                          (.append s " ")
                          (.append s (case (last args)
                                       :soft "soft"
                                       :hard "hard")))

           (add-args! s args))
         (.append s ")\n"))
       (.append s "*/")
       [(.toString s)])))
 :raw)

;; --------------------
;; preformatted queries

(defn preformat [q]
  {:pre [(map? q)]
   ;; Ensure that we don't accidentally encode a variable
   ;; into the query. If you need to embed a constant use :inline
   ;; Bad: {:select :* :where [:= :type "my-type"]
   ;; Good: {:select :* :where [:= :type [:inline "my-type"]]}
   :post [(every? (fn [arg] (::hsql/wrapper (meta arg)))
                  (rest %))]}
  (binding [hsql/*clause-order* (deref (deref #'hsql/current-clause-order))]
    (hsql/format-dsl q nil)))

(defn formatp
  "Takes a preformatted query, generated by `preformat` and a map of
   keyword params and returns a query you can pass to sql/query.

   (let [q {:select :* :from :users :where [:= :id :?id]}
         pq (preformat q)]
     (is (= (formatp pq {:id 1})
            (hsql/format q {:params {:id 1}}))))

  Does not support any honeysql options. Only supports map-based queries.

  You must use predefined params, e.g. `[:= :id :?id]` not `[:= :id 1]`"
  [preformatted-query params]
  (binding [hsql/*params* params]
    (mapv #(#'hsql/unwrap % {}) preformatted-query)))
